package com.qkl.upload.publics.utils;

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
import java.util.HashMap;
import java.util.Map;

/**
 * 支持AES、DES、RSA加密、数字签名以及生成对称密钥和非对称密钥对
 */
public class CommonCryptoUtils {

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static final Encoder BASE64_ENCODER = Base64.getEncoder();
    private static final Decoder BASE64_DECODER = Base64.getDecoder();
    private static final Map<Algorithm, Cipher> CIPHER_CACHE = new HashMap<>();





    /**
     * 对称加密
     * @param secretKey 密钥
     * @param iv 加密向量，只有CBC模式才支持，如果是CBC则必传
     * @param plainText 明文
     * @param algorithm 对称加密算法，如AES、DES
     * @return
     * @throws Exception
     */
    public static String encryptSymmetrically(String secretKey, String iv, String plainText, Algorithm algorithm) throws Exception {
        SecretKey key = decodeSymmetricKey(secretKey, algorithm);
        IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
        byte[] plainTextInBytes = plainText.getBytes(DEFAULT_CHARSET);
        byte[] ciphertextInBytes = transform(algorithm, Cipher.ENCRYPT_MODE, key, ivParameterSpec, plainTextInBytes);

        return BASE64_ENCODER.encodeToString(ciphertextInBytes);
    }

    /**
     * 对称解密
     * @param secretKey 密钥
     * @param iv 加密向量，只有CBC模式才支持，如果是CBC则必传
     * @param ciphertext 密文
     * @param algorithm 对称加密算法，如AES、DES
     * @return
     * @throws Exception
     */
    public static String decryptSymmetrically(String secretKey, String iv, String ciphertext, Algorithm algorithm) throws Exception {
        SecretKey key = decodeSymmetricKey(secretKey, algorithm);
        IvParameterSpec ivParameterSpec = StringUtils.isBlank(iv) ? null : decodeIv(iv);
        byte[] ciphertextInBytes = BASE64_DECODER.decode(ciphertext);
        byte[] plainTextInBytes = transform(algorithm, Cipher.DECRYPT_MODE, key, ivParameterSpec, ciphertextInBytes);
        return new String(plainTextInBytes, DEFAULT_CHARSET);
    }






    /**
     * 将密钥进行Base64位解码，重新生成SecretKey实例
     * @param secretKey 密钥
     * @param algorithm 算法
     * @return
     */
    private static SecretKey decodeSymmetricKey(String secretKey, Algorithm algorithm) {
        byte[] key = BASE64_DECODER.decode(secretKey);
        return new SecretKeySpec(key, algorithm.getName());
    }

    private static IvParameterSpec decodeIv(String iv) {
        byte[] ivInBytes = BASE64_DECODER.decode(iv);
        return new IvParameterSpec(ivInBytes);
    }




    private static byte[] transform(Algorithm algorithm, int mode, Key key, IvParameterSpec iv, byte[] msg) throws Exception {
        Cipher cipher = CIPHER_CACHE.get(algorithm);
        // double check，减少上下文切换
        if (cipher == null) {
            synchronized (CommonCryptoUtils.class) {
                if ((cipher = CIPHER_CACHE.get(algorithm)) == null) {
                    cipher = determineWhichCipherToUse(algorithm);
                    CIPHER_CACHE.put(algorithm, cipher);
                }
                cipher.init(mode, key, iv);
                return cipher.doFinal(msg);
            }
        }

        synchronized (CommonCryptoUtils.class) {
            cipher.init(mode, key, iv);
            return cipher.doFinal(msg);
        }
    }

    private static Cipher determineWhichCipherToUse(Algorithm algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
        Cipher cipher;
        String transformation = algorithm.getTransformation();
        // 官方推荐的transformation使用algorithm/mode/padding组合，SunJCE使用ECB作为默认模式，使用PKCS5Padding作为默认填充
        if (StringUtils.isNotEmpty(transformation)) {
            cipher = Cipher.getInstance(transformation);
        } else {
            cipher = Cipher.getInstance(algorithm.getName());
        }

        return cipher;
    }

    public static class Algorithm {

        public interface Encryption {
            Algorithm AES_ECB_PKCS5 = new Algorithm("AES", "AES/ECB/PKCS5Padding", 128);//128
        }


        @Getter
        private String name;
        @Getter
        private String transformation;
        @Getter
        private int keySize;

        public Algorithm(String name, String transformation, int keySize) {
            this.name = name;
            this.transformation = transformation;
            this.keySize = keySize;
        }

    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class AsymmetricKeyPair {

        private String publicKey;
        private String privateKey;
    }

    private static final String  KEY_DEFAULT = "RWzwj39TCM5W1a6dDg3SSw==";
    /**
     *
     * @param ciphertext 密文
     * @return
     */
    private static String decrypt(String ciphertext){
        return decrypt(KEY_DEFAULT,ciphertext);
    }
    private static String decrypt(String key,String ciphertext){
        try{
            return decryptSymmetrically(key,null,ciphertext, Algorithm.Encryption.AES_ECB_PKCS5);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "";
    }

    /**
     *
     * @param ciphertext 明文
     * @return
     */
    private static String encrypt(String ciphertext) {
        return encrypt(KEY_DEFAULT, ciphertext);
    }

    private static String encrypt(String key,String ciphertext){
        try{
            return encryptSymmetrically(key,null,ciphertext, Algorithm.Encryption.AES_ECB_PKCS5);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "";
    }

    private static String generateSymmetricKey(Algorithm algorithm) throws NoSuchAlgorithmException {
        KeyGenerator generator = KeyGenerator.getInstance(algorithm.getName());
        generator.init(algorithm.getKeySize());
        SecretKey secretKey = generator.generateKey();
        return BASE64_ENCODER.encodeToString(secretKey.getEncoded());
    }

    private static SecureRandom RANDOM = new SecureRandom();
    private static String genIdStr(){
        long currTime = System.currentTimeMillis();
        int ran = RANDOM.nextInt(10000);
        return String.valueOf(currTime) + ran;
    }


    /**
     * 上传用
     */
    private static final String KEY_UPLOAD = "rSA0H5spCgSxFbawucHWhw==";
    public static String encryptEncodedUpload(String userName){
        try {
            return URLEncoder.encode(encryptUpload(userName), "UTF-8");
        }catch (Exception e){

        }
        return "";
    }
    public static String encryptUpload(String userName){
        UploadCrypto crypto = new UploadCrypto();
        crypto.setNonce(genIdStr());
        crypto.setTimestamp(System.currentTimeMillis());
        crypto.setUserName( StringUtils.trimToEmpty(userName));
        return encrypt(KEY_UPLOAD,JSON.toJSONString(crypto));
    }
    public static UploadCrypto decryptUpload(String sing){
        try {
            return JSON.parseObject(decrypt(KEY_UPLOAD,sing),UploadCrypto.class);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }


    public static void main(String[] args) throws Exception{
        //String secretKey = CommonCryptoUtils.generateSymmetricKey(Algorithm.Encryption.AES_ECB_PKCS5);//Algorithm.Encryption.AES_ECB_PKCS5
        //System.out.println(secretKey);

        String encrypt = CommonCryptoUtils.encryptUpload("胡义明");
        System.out.println(encrypt);
        System.out.println(decryptUpload(encrypt));

        System.out.println(CommonCryptoUtils.encryptEncodedUpload("胡义明"));
    }

    @Data
    public static class UploadCrypto{

        private static final int TIMEOUT = 5 * 60 * 60 * 1000;

        private String nonce;

        private String userName;

        private long timestamp;

        public boolean timeout(){
            return (System.currentTimeMillis() - timestamp) > TIMEOUT;
        }

    }

}


